#include "sdl_surface.h"


sdl::Surface::Surface()
: _surface(NULL)
, _depthBuffer(NULL)
{
	sdl::Event::getInstance()->addListener(this);
}


sdl::Surface::~Surface() throw()
{
	freeDepthBuffer();
}


void sdl::Surface::freeDepthBuffer()
{
	if (_depthBuffer) {
		int i = getHeight() - 1;
		for (; i >= 0; i--) {
			delete[] _depthBuffer[i];
		}
		delete[] _depthBuffer;
		_depthBuffer = NULL;
	}
}


void sdl::Surface::setVideoMode(
    int width, int height, Uint32 flags) throw (sdl::Exception)
{
	freeDepthBuffer();
	_surface = SDL_SetVideoMode(width, height, 32, SDL_SWSURFACE | flags);
	if (_surface == NULL)
		throw sdl::Exception("could not set video mode");
	if (flags & USE_DEPTH_BUFFER) {
		// Allocate depth buffer
		_depthBuffer = new float*[height];
		for (int i = 0; i < height; i++) {
			_depthBuffer[i] = new float[width];
		}
	}
}


void sdl::Surface::putPixel(int x, int y, const glm::vec3& color)
{
	if (x < 0 || x >= _surface->w || y < 0 || y >= _surface->h)
		return;

	Uint8 r = Uint8(glm::clamp(255 * color.r, 0.f, 255.f));
	Uint8 g = Uint8(glm::clamp(255 * color.g, 0.f, 255.f));
	Uint8 b = Uint8(glm::clamp(255 * color.b, 0.f, 255.f));

	Uint32* p = (Uint32*)_surface->pixels + y * _surface->pitch / 4 + x;
	*p = SDL_MapRGB(_surface->format, r, g, b);
}

void sdl::Surface::getpixel(int x, int y, glm::vec3* color)
{
    unsigned int pixel = ((unsigned int*)_surface->pixels)
                [y * (_surface->pitch / sizeof(unsigned int)) + x];
    color->r = ((pixel >> 16) & 0xff) / 255.f;
    color->g = ((pixel >> 8) & 0xff) / 255.f;
    color->b = (pixel & 0xff) / 255.f;
}


void sdl::Surface::fill(SDL_Rect* rc, Uint32 color)
{
	SDL_FillRect(_surface, rc, color);
}


void sdl::Surface::drawLine(int x0, int y0, int x1, int y1, const glm::vec3& color)
{
	int dx = abs(x1 - x0);
	int dy = abs(y1 - y0);
	int sx = (x0 < x1) ? 1 : -1;
	int sy = (y0 < y1) ? 1 : -1;
	int err = dx - dy;
	int e2;

	for (;;) {
		putPixel(x0, y0, color);
		if (x0 == x1 && y0 == y1)
			return;
		e2 = err << 1;
		if (e2 > -dy) {
			err -= dy;
			x0 += sx;
		}
		if (e2 < dx) {
			err += dx;
			y0 += sy;
		}
	}
}


void sdl::Surface::drawLine(
	const glm::vec3& begin, const glm::vec3& end, const Shader& col)
{
	glm::vec3 p0(round(begin.x), round(begin.y), begin.z);
	glm::vec3 p1(round(end.x), round(end.y), end.z);
	glm::vec3 delta(p1 - p0);

	float nrPoints = std::max(abs(delta.x), abs(delta.y));
	glm::vec3 step(delta / nrPoints);
	glm::vec3 c;
	float i = 0;
	int x, y;

	for (; i <= nrPoints; i++, p0 += step) {
		x = round(p0.x);
		y = round(p0.y);
		if (x < 0 || x >= _surface->w ||
			y < 0 || y >= _surface->h)
			continue;
		if (_depthBuffer) {
			if (p0.z >= _depthBuffer[y][x])
				continue;
			_depthBuffer[y][x] = p0.z;
		}
		col.transform(p0, &c);
		putPixel(x, y, c);
	}
}


void sdl::Surface::drawHorLine(int x1, int x2, int y, const glm::vec3& col)
{
	if (x2 < x1)
		std::swap(x1, x2);
	for (; x1 <= x2; x1++) {
		putPixel(x1, y, col);
	}
}


void sdl::Surface::saveBmp(const char* fname)
{
	SDL_SaveBMP(_surface, fname);
}


void sdl::Surface::update()
{
	float m = std::numeric_limits<float>::max();

	if (SDL_MUSTLOCK(_surface))
		SDL_LockSurface(_surface);

	if (_depthBuffer) {
		int w = getWidth();
		int i = getHeight() - 1;
		for (; i >= 0; i--) {
			std::fill_n(_depthBuffer[i], w, m);
		}
	}

	// Notify listeners
	fire(sdl::SurfaceListener::Update(), this);

	if (SDL_MUSTLOCK(_surface))
		SDL_UnlockSurface(_surface);
	SDL_UpdateRect(_surface, 0, 0, 0, 0);
}


void sdl::Surface::on(sdl::EventListener::NewEvent, const SDL_Event* e) throw()
{
	switch (e->type) {
	case SDL_VIDEORESIZE:
	case SDL_VIDEOEXPOSE:
		// Redraw
		update();
		break;
	}
}

